iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Modern Web

成為Canvas Ninja ! ~ 理解2D渲染的精髓系列 第 19

Day19 - 中場休息時間 - 怎麼樣用Canvas精準的寫出一個『字』 - 成為Canvas Ninja ~ 理解2D渲染的精髓

  • 分享至 

  • xImage
  •  

呃,首先呢~

敝人小弟在下我今天仔細的思考了一下,決定這次還是再來一篇『中場休息』科普文,等到明天再來繼續磁力/引力的部分

其實是因為剛好工作應接不暇來不及寫新的案例 = = (噓

Canvas文字的藝術

這次的中場休息系列,我們要來探討的是要怎麼樣在Canvas上面繪製文字

講到這邊大概有很多人會覺得超級莫名其妙:

阿不就是用ctx.fillText就解決了嗎? 還要討論什麼?

不要急嘛。

通常初次使用fillText,正常人應該會很直觀的就這樣寫:

function text(ctx,textSource){
  ctx.font = '50px serif';
  ctx.fillStyle="black";
  ctx.fillText(textSource,0,0);
}

(()=>{
  const ctx = document.querySelector('canvas').getContext('2d') ;
  text(ctx,'TEST')
})()

接著打開瀏覽器,然後就發現螢幕上面什麼都沒出現 ~

img

為什麼?看起來該做的都做了啊,怎麼什麼都沒長出來?

其實第一個原因就在於ctx.fillText的第2 、 3個參數,這個x = 0, y = 0的基準點,其實預設並不是從該行文字左上角作為頂點起算,而是從左下角起算。

有些人接著說,『那這樣是不是我隨意地給Y一個足夠大的數值,就可以了?』

OK~那就改成這樣

function text(ctx,textSource){
  ctx.font = '50px serif';
  ctx.fillStyle="black";
  ctx.fillText(textSource,0,50);
}

(()=>{
  const ctx = document.querySelector('canvas').getContext('2d') ;
  text(ctx,'TEST')
})()

img

文字這樣就出來了~!這樣就可以打完收工了嗎?! YAH! 今天真是EASY!

『錯誤。』

這邊很明顯的問題就是50 這個數字是我們隨便亂給的,實際上你並不知道Y應該要給多少才能夠完整剛好的把文字show出來。

那麼應該怎麼做呢?

請使用ctx.textBaseline 這個屬性,ctx.textBaseline是用來決定要把文字哪一處作為垂直渲染基準線property,而當我們把ctx.textBaseline設定在top的時候,文字就會以上緣作為垂直渲染基準線,這樣就可以看到我們繪製的文字了~。

延伸閱讀: MDN 上的 ctx.textBaseline: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

而既然我們提到了垂直渲染基準線,那肯定就也有水平渲染基準線的設置方式~
那就是ctx.textAlign,這個的用法其實就跟css差不多~

延伸閱讀: MDN 上的 ctx.textAlign: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign

那今天就到這邊了嗎 ~ 好像也不怎麼難嘛哈哈~

『錯誤。』

眼比較尖的人可能會注意到~我們用fillText寫出來的字,好像跟一般html打出來的文字比起來:

就是有那麼一點糊糊

到這邊可能就有些人開始翻文件找是不是有什麼反鋸齒啦~提升解析度property~但是在找了一陣子之後才發現根本不如所想,接下來可能就開始懷疑是不是硬體效能問題。

咦?你說我怎麼知道你心裡在想什麼? 那當然是因為小弟我第一次用fillText時就是撞死在這面牆上 :P

不知道大家還記不記得我們在這個系列文初期的時候有提到過:『一張寬度100px, 高度100px的canvas,它實際上就是100*100 = 10000個像素的集合體』。

記得啊。然後呢?

其實,瀏覽器畫面本身也可以看作是一個比較大號的Canvas,但不同的點之一就在於它的像素密度可能比一般的Canvas還要高。

為什麼說『可能』呢,原因其實就是我們剛剛提到的硬體差異

一般來說,如果使用者的電腦螢幕是Retina螢幕,或是其他類型的高解析度顯示器,他的螢幕像素密度會是一般顯示器,或是Canvas2倍以上。

這就意味著在同樣100x100範圍中繪製的文字,高解析度顯示器的瀏覽器畫面實際上是顯示了至少20000個像素,這就導致用DOM渲染的文字,畫質遠比用Canvas渲染的文字來的清晰。

但是這個狀況當然也有解決辦法,那就是強制對Canvas進行壓縮處理

還記得我們之前有提到過用css的方式去擴大Canvas的尺寸會導致像素密度變低嗎? 這邊我們就是要逆轉這個現象,首先把Canvas的width/height屬性都提高N倍,然後再用css去把style的width/height再縮減N倍。

這麼一來,像素的密度就直接被提高N倍了,而且Canvas的長寬還不變。

接著可能有人會問:

有沒有辦法求得N實際上應該要給多少才夠呢,給太大也不好吧?

這時候就該Window.devicePixelRatio這個property登場啦~

延伸閱讀: MDN上的Window.devicePixelRatio: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio

Window.devicePixelRatio這個property就是可以用來偵測當前的顯示器像素密度值,例如,Macbook ProRetina螢幕的這個值會是2,而其他種類的顯示器也有不同的值。

妥善的運用Window.devicePixelRatio,我們就可以在Canvas上畫出跟Dom渲染文字具有同等水平解析度的文字~

到這邊為止,我們已經學到怎麼用Canvas去畫一行清晰的文字,但是實際上我們今天的內容~

『還沒有結束。』

我們今天的目標是「用Canvas精準的寫出一個『字』」,但是我們從剛剛到現在,其實都還是在一張固定大小的Canvas上繪製文字。

試想今天如果有一個需求:『如果我需要用Canvas去畫一個字,而畫好字的canvas,他的長寬尺寸要完全貼合這個畫出來的字,要怎麼辦?』

碰到像這樣的狀況,就代表著我們需要獲取『字』的『高度』/『寬度』等數據。

這時候可能就會有人懷疑:

有可能取得這些東西嗎? 我在用DOM渲染文字的時候從沒聽說過有這種數據可以取得

基本上,api其實都是應需求而生的,而在canvas的世界中,想要去獲取當下所渲染出來的一個文字的高度/寬度,當然也是合情合理~

這邊要登場的就是ctx.measureText() 這個api

延伸閱讀: MDN上的ctx.measureText: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText

這個api的用法就在於可以傳入一個字串,然後他就會根據當前ctx的font屬性設置的字體大小等參數,來回傳一個TextMetrics 物件。

什麼是TextMetrics 物件? 這是一種包含了ctx環境下渲染某字串的各項視覺數值的一個物件,除了基本的長寬,還能透過部分數值計算出很多人在菜鳥時期可能都想要計算過的文字實際高度(就是文字實際具有顏色的部分所佔的高度,不是行高, 也不是X高~!)

延伸閱讀: MDN上的TextMetrics物件介紹: https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics

img

透過上述的方法,我們其實就可以利用Canvas做出近似用DOM渲染的文字,這樣做的好處就是我們還可以對這樣的Canvas文字作進一步加工! 例如填充漸層填充素材圖樣,甚至可以做出文字變形動畫!(然後再把做好的Canvas轉成inline-block當成行內元素塞到其他元素當成段落文字的一部分)

小結

以上就是我們第二篇中場休息系列文,雖然是有點想放一些實作的案例,但是礙於時間問題沒有完成(之前是有在公司專案實作過用Canvas做漸層文字的IE11 PolyFill~效果真的不錯),總之還是希望大家喜歡這次的科普介紹~


上一篇
Day18 - 物理模擬篇 - 彈力、引力與磁力III - 成為Canvas Ninja ~ 理解2D渲染的精髓
下一篇
Day20 - 物理模擬篇 - 彈力、引力與磁力IV - 成為Canvas Ninja ~ 理解2D渲染的精髓
系列文
成為Canvas Ninja ! ~ 理解2D渲染的精髓31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言